package com.wl.spring;

import com.wl.spring.web.MyAnnotationConfigApplicationContest;
import lombok.Data;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.lang.reflect.Method;
import java.util.*;

public class AopApplicationContext
        extends MyAnnotationConfigApplicationContest{

    // 切面对象
    List<Object> aspectBeans = new ArrayList<>();
    // 切点 + 切面信息
    Set<AspectInfo> aspects = new HashSet<>();
    // 需要被代理的对象
    Map<String, Object> proxyBeanMap = new HashMap<>();

    public AopApplicationContext(Class configClass) throws Exception {
        super(configClass);
        // 自定义AOP
        // 1. 筛选 切面对象 @Aspect
        for (Object bean : singtonObjects.values()) {
            if (bean.getClass().getAnnotation(Aspect.class)!=null) {
                aspectBeans.add(bean);
            }
        }
        // 2. 分析 切面对象 提取切点
        for (Object aspectBean : aspectBeans) {
            Method[] methods = aspectBean.getClass().getDeclaredMethods();
            for (Method method : methods) {
                // 只关注 增强方法  @Before, @After ...
                if(method.getAnnotation(Before.class) != null){
                    // 前置增强方法
                    AspectInfo aspectInfo = new AspectInfo();
                    aspectInfo.adviceMethod = method;
                    aspectInfo.aspectBean = aspectBean;
                    Before before = method.getAnnotation(Before.class);
                    aspectInfo.pointcut = before.value();
                    aspects.add(aspectInfo);
                    aspectInfo.replacePointcut();
                } else if(method.getAnnotation(After.class) != null){
                    // 后增强方法
                    AspectInfo aspectInfo = new AspectInfo();
                    aspectInfo.adviceMethod = method;
                    aspectInfo.aspectBean = aspectBean;
                    After after = method.getAnnotation(After.class);
                    aspectInfo.pointcut = after.value();
                    aspects.add(aspectInfo);
                    aspectInfo.replacePointcut();
                } else {
                    // TODO 其他增强请自行扩展
                }
            }
        }
        System.out.println("-------------- 切面对象 ------------");
        aspectBeans.forEach(System.out::println);
        System.out.println("-------------- 切面信息 ------------");
        aspects.forEach(System.out::println);

        // 3. 通过切点判断 IOC 容器中的对象, 哪些对象包含切到的方法
        for (Map.Entry<String, Object> entry : singtonObjects.entrySet()) {
            Object bean = entry.getValue();
            String id = entry.getKey();
            a:for (AspectInfo aspect : aspects) {
                String pointcut = aspect.pointcut;
                // pointcut => * com.ly..*Mapper.*(..)
                pointcut = pointcut.replaceFirst("execution\\((.+)\\)","$1");
                String pointcutRegex = pointcut.replaceAll("\\.{2}","\\.\\*");
                pointcutRegex = pointcutRegex.replaceAll("\\*","\\.\\*");
                System.out.println("pointcutRegex = " + pointcutRegex);
                // 对业务对象的方法进行遍历
                for (Method method : bean.getClass().getDeclaredMethods()) {
                    String methodStr = method.toString();
                    System.out.println("methodStr = " + methodStr);
                    if (methodStr.matches(pointcutRegex)) {
                        System.out.printf("切到的bean %s => %s", id, bean);
                        // 被切到的方法
                        proxyBeanMap.put(id, bean);
                        break a;
                    }
                }
            }
        }



        // 4. 包含被切到的方法的对象 => 增强 (JDK or CGLIB)
        // 5. 替换原有业务对象


    }

    public static void main(String[] args) throws Exception {
        AopApplicationContext cxt = new AopApplicationContext(AopConfig.class);
    }
}

@Data
class AspectInfo {
    String pointcut;       // 切点
    Method adviceMethod;  // 增强方法
    Object aspectBean;    // 切面对象

    // 替换切点表达式
    void replacePointcut() throws NoSuchMethodException {
        if(pointcut.matches("(\\w+)\\(\\)")){
            String pointcutMethod = pointcut.replaceFirst("(\\w+)\\(\\)","$1");
            Method method = aspectBean.getClass().getDeclaredMethod(pointcutMethod);
            Pointcut pointcutAnno = method.getAnnotation(Pointcut.class);
            pointcut = pointcutAnno.value();
        }
    }
}
